package com.gitee.wsl.compose.chart.pie

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathOperation
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.withSaveLayer
import androidx.compose.ui.semantics.Role
import com.gitee.wsl.compose.chart.ExperimentalKoalaPlotApi
import com.gitee.wsl.common.ui.ext.shape.Slice
import com.gitee.wsl.compose.chart.SelectChangedListener
import com.gitee.wsl.compose.chart.generateHueColorPalette
import com.gitee.wsl.compose.chart.pie.PieSlice.defaultHoverExpandFactor
import com.gitee.wsl.unit.angle.AngleUnit
import com.gitee.wsl.unit.angle.ext.minus
import com.gitee.wsl.unit.angle.ext.plus
import com.gitee.wsl.unit.angle.ext.times

object PieSlice{
    const val defaultHoverExpandFactor = 1.2f
}

typealias PieSliceSlate = @Composable PieSliceScope.(Int,selected:Boolean, SelectChangedListener?) -> Unit


@OptIn(ExperimentalKoalaPlotApi::class)
fun PieSlice.createDefaultSlice(
    size:Int,
    antiAlias: Boolean = true,
    sliceFactory:SliceFactory = PieSlice.defaultSliceFactory,
    borderFactory: ((index:Int) ->BorderStroke?)? = null,
    gapFactory: ((index:Int)->AngleUnit) = { AngleUnit.ZERO },
    hoverExpandFactor: Float = defaultHoverExpandFactor,
    use3D: Boolean = false,
    hoverElementSlate: @Composable ((index:Int) -> Unit)? = null,
):  PieSliceSlate =  { index,selected,listener->
    val colors = remember(size) { generateHueColorPalette(size) }
    DefaultSlice(
        color = colors[index],
        border = borderFactory?.invoke(index),
        antiAlias = antiAlias,
        hoverExpandFactor = hoverExpandFactor,
        hoverElement = { hoverElementSlate?.invoke(index) },
        gap = gapFactory.invoke(index),
        selected = selected,
        clickable = listener != null,
        use3D = use3D,
        sliceFactory = sliceFactory,
        onClick = {
            listener?.invoke(index)
        }
    )
}

typealias SliceFactory = (startAngle: AngleUnit,
                          angle: AngleUnit,
                          innerRadius: Float,
                          outerRadius: Float,) -> Shape

val PieSlice.defaultSliceFactory:SliceFactory
          get() = { startAngle: AngleUnit,angle: AngleUnit,
                    innerRadius: Float,outerRadius: Float->
              Slice(startAngle, angle, innerRadius, outerRadius)
          }

/**
 * A default pie chart slice implementation that can form full slices as well as slices
 * with a "hole" for donut charts.
 *
 * @receiver Provides drawing and interaction parameters for the slice scope
 * @param color The Color of the Slice
 * @param modifier The modifier to be applied to this item
 * @param border The border stroke for the Slice
 * @param hoverExpandFactor Amount the slice expands when hovered. 1 is no expansion, values greater
 * than 1 expand outward from the pie, and values less than 1 shrink. If expansion on hover is
 * desired, a good starting value is 1.05.
 * @param hoverElement Content to show when the mouse/pointer hovers over the slice
 * @param clickable If clicking should be enabled.
 * @param antiAlias Set to true if the slice should be drawn with anti-aliasing, false otherwise
 * @param gap Specifies the gap between slices. It is the angular distance, in degrees, between the
 * start/stop values the slice represents and where the slice is actually drawn.
 * @param onClick handler of clicks on the slice
 */
@ExperimentalKoalaPlotApi
@Composable
fun PieSliceScope.DefaultSlice(
    color: Color,
    modifier: Modifier = Modifier,
    border: BorderStroke? = null,
    hoverExpandFactor: Float = defaultHoverExpandFactor,
    hoverElement: @Composable () -> Unit = {},
    clickable: Boolean = false,
    antiAlias: Boolean = true,
    gap: AngleUnit = AngleUnit.ZERO,
    selected: Boolean = false,
    sliceFactory:SliceFactory = PieSlice.defaultSliceFactory,
    use3D:Boolean = false,
    onClick: () -> Unit = {}
) {
    val interactionSource = remember { MutableInteractionSource() }
    val isHovered by interactionSource.collectIsHoveredAsState()
    val targetOuterRadius by animateFloatAsState(outerRadius * if (isHovered or selected) hoverExpandFactor else 1f)

    val innerRadiusValue = innerRadius * if (isHovered or selected) hoverExpandFactor else 1f

    val shape = sliceFactory(
        pieSliceData.startAngle + gap,
        pieSliceData.angle - 2 * gap,
        innerRadiusValue,
        targetOuterRadius
    )

    Box(
        modifier = modifier.fillMaxSize()
            .drawWithContent {
                drawIntoCanvas {
                    val path = (shape.createOutline(size, layoutDirection, this) as Outline.Generic).path

                    // draw slice
                    if(use3D){
                        //渲染层数
                        val mRender3DLevel = 15

                        (0 until mRender3DLevel).forEach { level ->
                            it.save()
                            it.translate(0f,(mRender3DLevel - level).toFloat())
                            // draw slice
                            it.drawPath(
                                path,
                                Paint().apply {
                                    isAntiAlias = antiAlias
                                    this.color = color
                                }
                            )
                            it.restore()
                        }

                    }else{
                        it.drawPath(
                            path,
                            Paint().apply {
                                isAntiAlias = antiAlias
                                this.color = color
                            }
                        )
                    }


                    if (border != null) {
                        it.withSaveLayer(Rect(Offset.Zero, size), Paint().apply { isAntiAlias = antiAlias }) {
                            val clip = Path().apply {
                                addRect(Rect(Offset.Zero, size))
                                op(this, path, PathOperation.Difference)
                            }

                            it.drawPath(
                                path,
                                Paint().apply {
                                    isAntiAlias = antiAlias
                                    strokeWidth = border.width.toPx()
                                    style = PaintingStyle.Stroke
                                    border.brush.applyTo(size, this, 1f)
                                }
                            )

                            // Remove part of border drawn outside of the slice bounds
                            it.drawPath(
                                clip,
                                Paint().apply {
                                    isAntiAlias = antiAlias
                                    blendMode = BlendMode.Clear
                                }
                            )
                        }
                    }
                }
                drawContent()
            }.clip(shape)
            .then(
                if (clickable) {
                    Modifier.clickable(
                        enabled = true,
                        role = Role.Button,
                        onClick = onClick
                    )
                } else {
                    Modifier
                }
            )
            .hoverableElement(hoverElement)
            .hoverable(interactionSource)
    ) {}
}